﻿using CityGrid.Common.Utilities;
using CQRSFramework.Common.Components;
using CQRSFramework.Domains;
using CQRSFramework.Events;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace CQRSFramework.Commands.Impl
{
    public class ProcessingCommandHandler : IProcessingCommandHandler
    {
        private readonly IEventService _eventService;
        private ConcurrentDictionary<Type, CommandExecutionInfo> _dic = new ConcurrentDictionary<Type, CommandExecutionInfo>();
        public ProcessingCommandHandler(IEventService eventService)
        {
            _eventService = eventService;
        }

        public void Handle(ProcessingCommand processingCommand)
        {
            var handlerType = typeof(ICommandHandler<>).MakeGenericType(processingCommand.Command.GetType());
            var handlers = ObjectContainer.ResolveAll(handlerType);
            if (handlers == null || handlers.Count() == 0)
            {
                throw new Exception($"No command handler found of command. commandType:{processingCommand.Command.GetType().Name}");
            }
            else if (handlers.Count() > 1)
            {
                throw new Exception($"More than one command handler data found. commandType:{processingCommand.Command.GetType().Name}");
            }

            var handler = handlers.First();
            var executionInfo = GetCommandExecutionInfo(processingCommand.Command.GetType());
            processingCommand.CommandContext.Clear();
            executionInfo.Invoke(handler, processingCommand.CommandContext, processingCommand.Command);

            var trackedAggregateRoot = processingCommand.CommandContext.GetTrackedAggregateRoots();
            var changedAggregateRootCount = 0;
            var changedAggregateRoot = default(IAggregateRoot);
            var changedEvents = default(IEnumerable<IDomainEvent>);
            foreach (var aggregateRoot in trackedAggregateRoot)
            {
                var events = aggregateRoot.GetUnCommittedDomainEvents();
                if (events.Any())
                {
                    changedAggregateRootCount++;
                    //约束：一个command只能修改一个聚合根
                    if (changedAggregateRootCount > 1)
                    {
                        throw new Exception($"Ditected more than one aggregateRoot created or modified by command. commandId:{processingCommand.Command.Id},commandType:{processingCommand.Command.GetType().Name}");
                    }
                    changedEvents = events;
                    changedAggregateRoot = aggregateRoot;
                }
            }
            _eventService.CommitDomainEvents(processingCommand.Command, changedAggregateRoot, changedEvents);
            changedAggregateRoot.AcceptDomainEvents(changedAggregateRoot.Version);
            _eventService.PublishDomainEvents(changedEvents);
            processingCommand.Channel.BasicAck(deliveryTag: processingCommand.EA.DeliveryTag, multiple: false);
        }

        class CommandExecutionInfo
        {
            public Type CommandHandlerType { get; set; }
            public Action<object, ICommandContext, ICommand> Invoke { get; set; }
        }
        private CommandExecutionInfo GetCommandExecutionInfo(Type type)
        {
            CommandExecutionInfo subscriberInfo;
            if (!_dic.TryGetValue(type, out subscriberInfo))
            {
                var handlerType = typeof(ICommandHandler<>).MakeGenericType(type);
                subscriberInfo = new CommandExecutionInfo
                {
                    CommandHandlerType = handlerType,
                    Invoke = DelegateFactory.CompileMethodInvocation<Action<object, ICommandContext, ICommand>>(handlerType, "Handle")
                };
                _dic.TryAdd(type, subscriberInfo);
            }
            return subscriberInfo;
        }
    }
}
